package cn.geminis.data.jpa.graphql.types;

import graphql.schema.Coercing;
import graphql.schema.CoercingParseValueException;
import graphql.schema.CoercingSerializeException;
import graphql.schema.GraphQLScalarType;
import org.springframework.util.Base64Utils;
import org.springframework.util.StringUtils;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Objects;

/**
 * @author puddi
 */
public class GraphQLTypes {

    public static final GraphQLScalarType GRAPHQL_BYTE_ARRAY = GraphQLScalarType.newScalar()
            .name("ByteArray")
            .description("map to byte[]")
            .coercing(new Coercing<byte[], String>() {

                private byte[] parse(Object input) {
                    if (input instanceof String) {
                        if (StringUtils.isEmpty(input)) {
                            return null;
                        } else {
                            return Base64Utils.decodeFromString((String) input);
                        }
                    } else if (Objects.isNull(input)) {
                        return null;
                    } else {
                        throw new CoercingParseValueException("Expected type 'String' but was '" + input.getClass().getName() + "'.");
                    }
                }

                @Override
                public String serialize(Object input) {
                    if (input instanceof byte[]) {
                        return Base64Utils.encodeToString((byte[]) input);
                    } else if (Objects.isNull(input)) {
                        return null;
                    } else {
                        throw new CoercingSerializeException("Expected type 'byte[]' but was '" + input.getClass().getName() + "'.");
                    }
                }

                @Override
                public byte[] parseValue(Object input) {
                    return parse(input);
                }

                @Override
                public byte[] parseLiteral(Object input) {
                    return parse(input);
                }
            })
            .build();

    public static final GraphQLScalarType GRAPHQL_LOCAL_DATE_TIME = GraphQLScalarType.newScalar()
            .name("LocalDateTime")
            .description("LocalDateTime type")
            .coercing(new Coercing<LocalDateTime, Long>() {

                private LocalDateTime parse(long input) {
                    var instant = Instant.ofEpochMilli(input);
                    return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
                }

                @Override
                public Long serialize(Object input) {
                    if (input instanceof LocalDateTime) {
                        var zonedDateTime = ZonedDateTime.of((LocalDateTime) input, ZoneId.systemDefault());
                        return zonedDateTime.toInstant().toEpochMilli();
                    } else {
                        throw new CoercingSerializeException("Expected type 'LocalDateTime' but was '" + input.getClass().getName() + "'.");
                    }
                }

                @Override
                public LocalDateTime parseValue(Object input) {
                    if (input instanceof Long) {
                        return parse((Long) input);
                    } else if (Objects.isNull(input)) {
                        return null;
                    } else {
                        throw new CoercingSerializeException("Expected type 'Long' but was '" + input.getClass().getName() + "'.");
                    }
                }

                @Override
                public LocalDateTime parseLiteral(Object input) {
                    if (input instanceof Long) {
                        return parse((Long) input);
                    } else if (Objects.isNull(input)) {
                        return null;
                    } else {
                        throw new CoercingSerializeException("Expected type 'Long' but was '" + input.getClass().getName() + "'.");
                    }
                }
            })
            .build();
}